use super::reader::Reader;
use super::Error;

// ## 2. Syntax
// ### 2.a. Comments
// ### 2.b. White space
// ### 2.c. Keywords
// ### 2.d. Special characters

#[derive(Debug, Clone, PartialEq)]
pub enum Event<'a> {
	Error(Error, (usize, usize)),
	Eof,

	Comment(&'a str),
	String(&'a str),
	Group(&'a str),
	Ident(&'a str),
	Number(&'a str), // inlucde $exp

	// Colon, // :
	Semi,        // ;
	Comma,       // ,
	Backslash,   // \
	Hyphen,      // -
	Eq,          // =
	LBrace,      // {
	RBrace,      // }
	LSquare,     // [
	RSquare,     // ]
	LParen,      // (
	RParen,      // )
	LAngle,      // <
	RAngle,      // >
	SingleQuote, // '

	// top-level keywords
	TableKw,
	LookupKw,
	LanguagesystemKw,
	AnchorDefKw,
	FeatureKw,
	MarkClassKw,
	AnonKw, // 'anon' and 'anonymous'
	ConditionSetKw,
	VariationKw,

	// other keywords
	AnchorKw,
	ByKw,
	ContourpointKw,
	CursiveKw,
	DeviceKw,
	EnumKw, // 'enum' and 'enumerate'
	ExcludeDfltKw,
	FromKw,
	IgnoreKw,
	IgnoreBaseGlyphsKw,
	IgnoreLigaturesKw,
	IgnoreMarksKw,
	IncludeKw,
	IncludeDfltKw,
	LanguageKw,
	LookupflagKw,
	MarkKw,
	MarkAttachmentTypeKw,
	NameIdKw,
	NullKw,
	ParametersKw,
	PosKw, // 'pos' and 'position'
	RequiredKw,
	RightToLeftKw,
	RsubKw, // 'rsub' and 'reversesub'
	ScriptKw,
	SubKw, // 'sub' and 'substitute'
	SubtableKw,
	UseExtensionKw,
	UseMarkFilteringSetKw,
	ValueRecordDefKw,
}
impl<'a> Event<'a> {
	// GPOS
	pub const BASE: Event<'static> = Event::Ident("base");
	pub const LIGATURE: Event<'static> = Event::Ident("ligature");
	// BASE
	pub const HORIZ_AXIS_BASE_SCRIPT_LIST: Event<'static> = Event::Ident("HorizAxis.BaseScriptList");
	pub const HORIZ_AXIS_BASE_TAG_LIST: Event<'static> = Event::Ident("HorizAxis.BaseTagList");
	pub const HORIZ_AXIS_MIN_MAX: Event<'static> = Event::Ident("HorizAxis.MinMax");
	pub const VERT_AXIS_BASE_SCRIPT_LIST: Event<'static> = Event::Ident("VertAxis.BaseScriptList");
	pub const VERT_AXIS_BASE_TAG_LIST: Event<'static> = Event::Ident("VertAxis.BaseTagList");
	pub const VERT_AXIS_MIN_MAX: Event<'static> = Event::Ident("VertAxis.MinMax");
	// GDEF
	pub const ATTACH: Event<'static> = Event::Ident("Attach");
	pub const GLYPH_CLASS_DEF: Event<'static> = Event::Ident("GlyphClassDef");
	pub const LIGATURE_CARET_BY_DEV: Event<'static> = Event::Ident("LigatureCaretByDev");
	pub const LIGATURE_CARET_BY_INDEX: Event<'static> = Event::Ident("LigatureCaretByIndex");
	pub const LIGATURE_CARET_BY_POS: Event<'static> = Event::Ident("LigatureCaretByPos");
	// head
	pub const FONT_REVISION: Event<'static> = Event::Ident("FontRevision");
	// hhea
	pub const ASCENDER: Event<'static> = Event::Ident("Ascender");
	pub const CARET_OFFSET: Event<'static> = Event::Ident("CaretOffset");
	pub const DESCENDER: Event<'static> = Event::Ident("Descender");
	pub const LINE_GAP: Event<'static> = Event::Ident("LineGap");
	// OS/2
	pub const CAP_HEIGHT: Event<'static> = Event::Ident("CapHeight");
	pub const CODE_PAGE_RANGE: Event<'static> = Event::Ident("CodePageRange");
	pub const PANOSE: Event<'static> = Event::Ident("Panose");
	pub const TYPO_ASCENDER: Event<'static> = Event::Ident("TypoAscender");
	pub const TYPO_DESCENDER: Event<'static> = Event::Ident("TypoDescender");
	pub const TYPO_LINE_GAP: Event<'static> = Event::Ident("TypoLineGap");
	pub const UNICODE_RANGE: Event<'static> = Event::Ident("UnicodeRange");
	pub const VENDOR: Event<'static> = Event::Ident("Vendor");
	pub const WIN_ASCENT: Event<'static> = Event::Ident("winAscent");
	pub const WIN_DESCENT: Event<'static> = Event::Ident("winDescent");
	pub const XHEIGHT: Event<'static> = Event::Ident("XHeight");
	// vhea
	pub const VERT_TYPO_ASCENDER: Event<'static> = Event::Ident("VertTypoAscender");
	pub const VERT_TYPO_DESCENDER: Event<'static> = Event::Ident("VertTypoDescender");
	pub const VERT_TYPO_LINE_GAP: Event<'static> = Event::Ident("VertTypoLineGap");
	pub const VERT_ADVANCE_Y: Event<'static> = Event::Ident("VertAdvanceY");
	pub const VERT_ORIGIN_Y: Event<'static> = Event::Ident("VertOriginY");
	// STAT
	pub const ELIDED_FALLBACK_NAME: Event<'static> = Event::Ident("ElidedFallbackName");
	pub const ELIDED_FALLBACK_NAME_ID: Event<'static> = Event::Ident("ElidedFallbackNameID");
	pub const DESIGN_AXIS: Event<'static> = Event::Ident("DesignAxis");
	pub const AXIS_VALUE: Event<'static> = Event::Ident("AxisValue");
	pub const FLAG: Event<'static> = Event::Ident("flag");
	pub const LOCATION: Event<'static> = Event::Ident("location");
	pub const ELIDABLE_AXIS_VALUE_NAME: Event<'static> = Event::Ident("ElidableAxisValueName");
	pub const OLDER_SIBLING_FONT_ATTRIBUTE: Event<'static> = Event::Ident("OlderSiblingFontAttribute");
	// size feature
	pub const SIZEMENUNAME: Event<'static> = Event::Ident("sizemenuname");

	pub fn from(word: &str) -> Option<Self> {
		match word {
			"anchor" => Some(Event::AnchorKw),
			"anchorDef" => Some(Event::AnchorDefKw),
			"anon" | "anonymous" => Some(Event::AnonKw),
			"conditionset" => Some(Event::ConditionSetKw),
			"variation" => Some(Event::VariationKw),
			"by" => Some(Event::ByKw),
			"contourpoint" => Some(Event::ContourpointKw),
			"cursive" => Some(Event::CursiveKw),
			"device" => Some(Event::DeviceKw), //[ Not implemented ];
			"enum" | "enumerate" => Some(Event::EnumKw),
			"exclude_dflt" | "excludeDFLT" => Some(Event::ExcludeDfltKw),
			"feature" => Some(Event::FeatureKw), //(used as a block and as a statement);
			"from" => Some(Event::FromKw),
			"ignore" => Some(Event::IgnoreKw), //(used with substitute and position);
			"IgnoreBaseGlyphs" => Some(Event::IgnoreBaseGlyphsKw),
			"IgnoreLigatures" => Some(Event::IgnoreLigaturesKw),
			"IgnoreMarks" => Some(Event::IgnoreMarksKw),
			"include" => Some(Event::IncludeKw),
			"include_dflt" | "includeDFLT" => Some(Event::IncludeDfltKw),
			"language" => Some(Event::LanguageKw),
			"languagesystem" => Some(Event::LanguagesystemKw),
			"lookup" => Some(Event::LookupKw),
			"lookupflag" => Some(Event::LookupflagKw),
			"mark" => Some(Event::MarkKw),
			"MarkAttachmentType" => Some(Event::MarkAttachmentTypeKw),
			"markClass" => Some(Event::MarkClassKw),
			"nameid" => Some(Event::NameIdKw),
			"NULL" => Some(Event::NullKw), //(used in substitute, device, value record, anchor);
			"parameters" => Some(Event::ParametersKw),
			"pos" | "position" => Some(Event::PosKw),
			"required" => Some(Event::RequiredKw), //[ Not implemented ];
			"reversesub" | "rsub" => Some(Event::RsubKw),
			"RightToLeft" => Some(Event::RightToLeftKw),
			"script" => Some(Event::ScriptKw),
			"substitute" | "sub" => Some(Event::SubKw),
			"subtable" => Some(Event::SubtableKw),
			"table" => Some(Event::TableKw),
			"useExtension" => Some(Event::UseExtensionKw),
			"UseMarkFilteringSet" => Some(Event::UseMarkFilteringSetKw),
			"valueRecordDef" => Some(Event::ValueRecordDefKw),
			_ => None,
		}
	}
}

pub struct Lexer<'a> {
	r: Reader<'a>,
	pub emit_comment: bool,
}
impl<'a> Lexer<'a> {
	pub fn new(content: &'a str) -> Self {
		Self {
			r: Reader::new(content),
			emit_comment: false,
		}
	}
	pub fn peek_many(&mut self) -> Self {
		let tail = self.r.tail();
		Self::new(tail)
	}
	pub fn read_file(&mut self) -> Result<&'a str, Error> {
		self.r.eat_while(is_ascii_whitespace);
		let v = self.r.capture(|r| r.eat_until_char(')')).ok_or(Error::Msg("expect file name"))?;
		Ok(v)
	}
	pub fn read_anon(&mut self, end: char, name: &str) -> Result<&'a str, Error> {
		let v = self
			.r
			.capture(|r| {
				let mut ret = true;
				loop {
					r.eat_until_char(end);
					ret &= r.eat_char(end);
					if !ret {
						break;
					}
					let mut xr = Reader::new(r.tail());
					xr.eat_while(is_ascii_whitespace);
					if xr.tail().starts_with(name) {
						break;
					}
				}
				ret
			})
			.ok_or(Error::Msg("expect block"))?;
		Ok(v)
	}
	pub fn read_next(&mut self) -> Result<Event<'a>, Error> {
		self.r.eat_while(is_ascii_whitespace);
		match self.r.peek().ok_or(Error::Eof)? {
			'-' => {
				let mut r = self.r.peek_many();
				r.next();
				let c0 = r.next().ok_or(Error::Msg("expect more after -"))?;
				if c0 == '0' {
					let c1 = r.next().ok_or(Error::Msg("expect more after -0"))?;
					// octal, so this is a hyphen (and an error)
					if c1.is_ascii_digit() {
						self.r.next();
						return Ok(Event::Hyphen);
					}
					// hex: ditto
					if ['x', 'X'].contains(&c1) {
						self.r.next();
						return Ok(Event::Hyphen);
					}
				}
				if c0.is_ascii_digit() {
					return self.number();
				}
				self.r.next();
				Ok(Event::Hyphen)
			}
			'0'..='9' => self.number(),
			'#' => self.comment(),
			'$' => self.exp(),
			'"' => self.string(),
			'\\' => match self.r.peek_many().nth(1) {
				Some(c) if c.is_ascii_digit() => self.cid(),
				_ => {
					self.r.next();
					Ok(Event::Ident(self.ident()?))
				}
			},
			';' => {
				self.r.next();
				Ok(Event::Semi)
			}
			',' => {
				self.r.next();
				Ok(Event::Comma)
			}
			'@' => {
				self.r.next();
				Ok(Event::Group(self.ident()?))
			}
			'=' => {
				self.r.next();
				Ok(Event::Eq)
			}
			'{' => {
				self.r.next();
				Ok(Event::LBrace)
			}
			'}' => {
				self.r.next();
				Ok(Event::RBrace)
			}
			'[' => {
				self.r.next();
				Ok(Event::LSquare)
			}
			']' => {
				self.r.next();
				Ok(Event::RSquare)
			}
			'(' => {
				self.r.next();
				Ok(Event::LParen)
			}
			')' => {
				self.r.next();
				Ok(Event::RParen)
			}
			'<' => {
				self.r.next();
				Ok(Event::LAngle)
			}
			'>' => {
				self.r.next();
				Ok(Event::RAngle)
			}
			'\'' => {
				self.r.next();
				Ok(Event::SingleQuote)
			}
			_ => match self.ident() {
				Ok(s) => match Event::from(s) {
					Some(e) => Ok(e),
					None => Ok(Event::Ident(s)),
				},
				Err(e) => {
					self.r.next();
					Err(e)
				}
			},
		}
	}
	pub fn expect(&mut self, expect: Event<'_>) -> Result<(), Error> {
		let e = self.read_next()?;
		if e != expect {
			return Err(Error::Expect(format!("{:?}", expect), Some(format!("{:?}", e))));
		}
		Ok(())
	}
	pub fn expect_ident(&mut self) -> Result<&'a str, Error> {
		match self.read_next()? {
			Event::Ident(s) => Ok(s),
			e => Err(Error::Expect("Event::Ident".to_string(), Some(format!("{:?}", e)))),
		}
	}
	pub fn expect_group(&mut self) -> Result<&'a str, Error> {
		match self.read_next()? {
			Event::Group(s) => Ok(s),
			e => Err(Error::Expect("Event::Group".to_string(), Some(format!("{:?}", e)))),
		}
	}
	pub fn expect_number(&mut self) -> Result<&'a str, Error> {
		match self.read_next()? {
			Event::Number(s) => Ok(s),
			e => Err(Error::Expect("Event::Number".to_string(), Some(format!("{:?}", e)))),
		}
	}
	fn comment(&mut self) -> Result<Event<'a>, Error> {
		self.r.next();
		let v = self.r.capture(|r| r.eat_comment()).unwrap_or_default();
		self.r.eat_any("\r\n");
		if self.emit_comment {
			Ok(Event::Comment(v))
		} else {
			self.read_next()
		}
	}
	fn string(&mut self) -> Result<Event<'a>, Error> {
		self.r.next();
		let v = self.r.capture(|r| r.eat_string()).unwrap_or_default();
		self.r.eat_char('"');
		Ok(Event::String(v))
	}
	fn ident(&mut self) -> Result<&'a str, Error> {
		self.r.capture(|r| r.eat_ident()).ok_or(Error::Msg("expect ident"))
	}
	fn number(&mut self) -> Result<Event<'a>, Error> {
		let v = self.r.capture(|r| r.eat_number()).ok_or(Error::Msg("expect number"))?;
		Ok(Event::Number(v))
	}
	fn cid(&mut self) -> Result<Event<'a>, Error> {
		let v = self.r.capture(|r| r.eat_cid()).ok_or(Error::Msg("expect digits"))?;
		Ok(Event::Ident(v))
	}
	fn exp(&mut self) -> Result<Event<'a>, Error> {
		let v = self.r.capture(|r| r.eat_exp()).ok_or(Error::Msg("expect glyphs exp"))?;
		Ok(Event::Number(v))
	}
}
impl<'a> core::iter::Iterator for Lexer<'a> {
	type Item = Event<'a>;
	fn next(&mut self) -> Option<Self::Item> {
		match self.read_next() {
			Ok(e) => Some(e),
			Err(Error::Eof) => None,
			Err(e) => Some(Event::Error(e, self.r.pos())),
		}
	}
}

impl<'a> Reader<'a> {
	fn eat_ident(&mut self) -> bool {
		loop {
			match self.peek() {
				None => break,
				Some(b) if is_ascii_whitespace(b) => break,
				Some('-') => (),
				Some(b) if is_special(b) => break,
				_ => (),
			}
			self.next();
		}
		true
	}
	fn eat_comment(&mut self) -> bool {
		loop {
			match self.peek() {
				None | Some('\n') | Some('\r') => break,
				_ => (),
			}
			self.next();
		}
		true
	}
	fn eat_string(&mut self) -> bool {
		loop {
			match self.peek() {
				Some('"') => {
					break true;
				}
				None => break false,
				_ => {
					self.next();
				}
			}
		}
	}
	fn eat_exp(&mut self) -> bool {
		self.eat_char('$') && {
			match self.peek() {
				Some('{') => self.eat_until_char('}') && self.eat_char('}'),
				_ => self.eat_ident(),
			}
		}
	}
	fn eat_cid(&mut self) -> bool {
		self.eat_char('\\') && self.eat_decimal_digits()
	}
	fn eat_number(&mut self) -> bool {
		self.eat_any("+-");
		let ret = match self.peek() {
			Some('0') => {
				self.eat_char('0');
				match self.peek() {
					Some('x') | Some('X') => {
						self.eat_any("xX");
						self.eat_hex_digits()
					}
					Some('b') => {
						self.eat_char('b');
						self.eat_binary_digits()
					}
					Some(c) if is_octal_digits(c) => {
						self.eat_octal_digits() //
					}
					_ => true, // 0 only
				}
			}
			Some('.') => {
				self.eat_decimal_digits() //
			}
			Some(c) if is_decimal_digit(c) => {
				self.eat_decimal_digits();
				match self.peek() {
					Some('.') => self.eat_decimal_digits(),
					_ => true,
				}
			}
			_ => false,
		};
		self.eat_any("udn");
		ret
	}
	fn eat_binary_digits(&mut self) -> bool {
		self.eat_any("01")
	}
	fn eat_octal_digits(&mut self) -> bool {
		self.eat_while(is_octal_digits)
	}
	fn eat_hex_digits(&mut self) -> bool {
		self.eat_while(is_hex_digit)
	}
	fn eat_decimal_digits(&mut self) -> bool {
		self.eat_while(is_decimal_digit)
	}
}
fn is_ascii_whitespace(c: char) -> bool {
	c == ' ' || ('\u{9}'..='\u{d}').contains(&c)
}
// [\ , ' - ; < = > @ \ ( ) [ ] { }]
fn is_special(c: char) -> bool {
	('\u{27}'..='\u{2d}').contains(&c)
		|| ('\u{3b}'..='\u{40}').contains(&c)
		|| ('\u{5b}'..='\u{5d}').contains(&c)
		|| '\u{7b}' == c
		|| '\u{7d}' == c
}
fn is_decimal_digit(c: char) -> bool {
	c.is_ascii_digit()
}
fn is_octal_digits(c: char) -> bool {
	('0'..='7').contains(&c)
}
fn is_hex_digit(c: char) -> bool {
	c.is_ascii_hexdigit()
}

#[cfg(test)]
mod tests {
	use super::*;
	#[test]
	fn load_fontinfotest() {
		let content = std::fs::read_to_string("assets/ufo/fontinfotest_v3.ufo/features.fea").unwrap();
		for e in Lexer::new(&content) {
			println!("{:?}", e);
		}
	}
	#[test]
	fn load_fea_goods() {
		let dir = std::fs::read_dir("assets/fea/parse-tests/good").unwrap();
		for file in dir.flatten() {
			let path = file.path();
			if file.file_type().unwrap().is_file() && path.extension().and_then(|e| e.to_str()) == Some("fea") {
				let content = std::fs::read_to_string(&path).unwrap();
				for p in Lexer::new(&content) {
					match p {
						Event::Error(e, p) => {
							panic!("{:?} {:?} {}", p, e, path.display());
						}
						_ => {}
					}
				}
			}
		}
	}
	#[test]
	fn load_glyphsapp_number_value() {
		let content = std::fs::read_to_string("assets/fea/parse-tests/good/spec8d.fea").unwrap();
		for e in Lexer::new(&content).filter(|e| !matches!(e, Event::Comment(_))) {
			match e {
				Event::Error(e, p) => {
					println!("{:?} {:?}", p, e);
					break;
				}
				_ => {
					println!("{:?}", e);
				}
			}
		}
	}
}
